package com.cellulam.framework.core.context;

import com.cellulam.framework.core.utils.JacksonUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class AppContextLifeCycleAspect {

    /**
     * 是否是schedule任务，默认为web应用
     */
    @Value("${schedule:false}")
    private boolean isSchedule;

    @Around(value = "@within(com.cellulam.framework.core.context.AppContextLifeCycle) || " +
            "@annotation(com.cellulam.framework.core.context.AppContextLifeCycle)")
    public Object aroundInit(ProceedingJoinPoint pjp) throws Throwable {
        AppContextLifeCycle appContextLifeCycle = this.getContextLifeCycle(pjp);
        initContext(appContextLifeCycle, pjp);

        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();
            this.logDigest(appContextLifeCycle, pjp, result, System.currentTimeMillis() - start);
            return result;
        } catch (Throwable e) {
            this.logDigest(appContextLifeCycle, pjp, null, e, System.currentTimeMillis() - start);
            throw e;
        } finally {
            clearContext(appContextLifeCycle, pjp);
        }
    }

    private AppContextLifeCycle getContextLifeCycle(ProceedingJoinPoint pjp) {

        MethodSignature signature = (MethodSignature) pjp.getSignature();

        return signature.getMethod().getAnnotation(AppContextLifeCycle.class);
    }

    private void initContext(AppContextLifeCycle appContextLifeCycle, ProceedingJoinPoint pjp) {

        if (appContextLifeCycle == null) {
            return;
        }

        if (appContextLifeCycle.onlySchedule() && !isSchedule) {
            return;
        }

        AppContext.initContext();
    }

    private void clearContext(AppContextLifeCycle appContextLifeCycle, ProceedingJoinPoint pjp) {

        if (appContextLifeCycle == null) {
            return;
        }

        if (appContextLifeCycle.onlySchedule() && !isSchedule) {
            return;
        }

        AppContext.clear();
    }

    private void logDigest(AppContextLifeCycle appContextLifeCycle, ProceedingJoinPoint pjp, Object result, Throwable e, long cost) {
        if (appContextLifeCycle == null) {
            return;
        }

        if (appContextLifeCycle.onlySchedule() && !isSchedule) {
            return;
        }

        if (!appContextLifeCycle.logDigest()) {
            return;
        }

        if (e == null && appContextLifeCycle.logDigestOnlyException()) {
            return;
        }

        Class<?> targetCls = pjp.getTarget().getClass();

        String targetClsName = targetCls.getName();
        String targetObjectMethodName = targetClsName + "." + pjp.getSignature().getName();

        String message = String.format("Invoke: [%s], params: [%s], result: [%s], cost: %s, context: [%s]",
                targetObjectMethodName,
                JacksonUtils.toJson(pjp.getArgs()),
                JacksonUtils.toJson(result),
                cost,
                AppContext.getDigest());

        if (e == null) {
            log.info(message);
        } else {
            log.error(message, e);
        }
    }

    private void logDigest(AppContextLifeCycle appContextLifeCycle, ProceedingJoinPoint pjp, Object result, long cost) {
        logDigest(appContextLifeCycle, pjp, result, null, cost);
    }
}
